概述

  如果在开发过程中,出现大量的if else或者switch case 语句,如果这些语句块中的代码并不是包含业务逻辑,只是单纯的分流方法,那么,每一个语句块中都是一个算法或者叫策略

背景

 

  比如在最近项目中遇到的问题。一个二维码字符串解析的方法:

    微信的二维码扫描结果包含“WeChat”,解析规则是拿着文本到微信服务器解析,返回解析对象

    支付宝二维码扫描结果包含“Alipay”,解析规则是使用“->”分割字符串得到解析对象。

  最简单快速的代码就是直接if else判断:

  

 1         /// <summary>
 2         /// 解析方法
 3         /// </summary>
 4         /// <param name="text">扫描得到的文本</param>
 5         public void AnalysisAction(string text)
 6         {
 7             //微信解析方法
 8             if (text.Contains("WeChat"))
 9             {
10                 //拿着text到微信服务器解析,返回解析对象。
11             }
12             //支付宝解析方法
13             else if (text.Contains("Alipay"))
14             {
15                 //使用->分割,得到解析对象。
16             }
17         }

 

 

 问题

  当然使用这种方式是可以的,但是如果以后又加入一种扫码解析方法:

  中国联通二维码扫描文本中包含“Unicom”,解析规则为以“:”分割,得到解析对象。

  那么你就要继续添加else if(text.Contains("Unicom"))。每次增加一个新的扫描解析规则,你都要去增加else if判断,这种是面向过程的体验,属于硬编码。这也违反了面向对象的开闭原则。

改进(抽象)

  我们可以使用策略模式来改进代码。义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

  抽象出一个扫描解析接口,定义一个解析方法。然后分别定义微信和支付宝的解析方法集成接口。

  策略模式的uml图如下:

  

 
    /// <summary>
    /// 扫描解析规则抽象接口
    /// </summary>
    public interface IStrategy
    {
        /// <summary>
        /// 扫描解析方法策略
        /// </summary>
        /// <param name="text"></param>
        void AnalysisAction(string text);
    }
扫描解析规则抽象接口
    /// <summary>
    /// 微信扫描解析规则
    /// </summary>
    public class StrategyWeChat : IStrategy
    {
        /// <summary>
        /// 扫描策略
        /// </summary>
        /// <param name="text"></param>
        public void AnalysisAction(string text)
        {
            //拿着text到微信服务器解析,返回解析对象。
        }
    }
微信扫描策略
    /// <summary>
    /// 支付宝扫描解析规则
    /// </summary>
    public class StrategyAlipay : IStrategy
    {
        /// <summary>
        /// 扫描策略
        /// </summary>
        /// <param name="text"></param>
        public void AnalysisAction(string text)
        {
            //使用->分割,得到解析对象。
        }
    }
支付宝扫描解析策略
    /// <summary>
    /// 接口管理类
    /// </summary>
    public class StrategyContext
    {
        private IStrategy strategy;
        /// <summary>
        /// 外层调用的时候决定使用哪个扫描策略
        /// </summary>
        /// <param name="strategy"></param>
        public StrategyContext(IStrategy strategy)
        {
            this.strategy = strategy;
        }
        public void AnalysisAction(string text)
        {
            strategy.AnalysisAction(text);
        }
    }
管理类

这样我们的业务逻辑就可以这样写:

        private StrategyContext Context;
        /// <summary>
        /// 解析方法
        /// </summary>
        /// <param name="text">扫描得到的文本</param>
        public void AnalysisAction(string text)
        {

            //微信解析方法
            if (text.Contains("WeChat"))
            {
                Context = new StrategyContext(new StrategyWeChat());
            }
            //支付宝解析方法
            else if (text.Contains("Alipay"))
            {
                Context = new StrategyContext(new StrategyAlipay());
            }
            Context.AnalysisAction(text);
        }

  我们将具体的解析规则放到了具体的实现类中,但是我们并没有消灭If else,如果在添加联通的扫码解析的话,还是需要修改代码,添加else。

  这就是策略模式的缺点,必须知道要使用的具体的策略,也就是有的人说的还是要使用if else。

升级改造

  因为前面说到了策略模式的缺点。如果就是要消灭if else呢?我们可以将决定使用策略的决定权放到具体策略实现类中。

 1     /// <summary>
 2     /// 扫描解析规则抽象接口
 3     /// </summary>
 4     public interface IStrategy
 5     {
 6         /// <summary>
 7         /// 是否可以解析
 8         /// </summary>
 9         bool Analysisable { get; }
10 
11         /// <summary>
12         /// 扫描解析方法策略
13         /// </summary>
14         /// <param name="text"></param>
15         void AnalysisAction();
16     }
扫描解析规则抽象接口添加是否可以解析属性
    /// <summary>
    /// 微信扫描解析规则
    /// </summary>
    public class StrategyWeChat : IStrategy
    {
        private string _text;
        public StrategyWeChat(string text)
        {
            this._text = text;
        }
        public bool Analysisable
        {
            get { return _text.Contains("WeChat"); }
        }

        /// <summary>
        /// 扫描策略
        /// </summary>
        /// <param name="text"></param>
        public void AnalysisAction()
        {
            //拿着_text到微信服务器解析,返回解析对象。
        }
    }
微信
    /// <summary>
    /// 支付宝扫描解析规则
    /// </summary>
    public class StrategyAlipay : IStrategy
    {
        private string _text;
        public StrategyAlipay(string text)
        {
            this._text = text;
        }
        public bool Analysisable
        {
            get { return _text.Contains("Alipay"); }
        }
        /// <summary>
        /// 扫描策略
        /// </summary>
        /// <param name="text"></param>
        public void AnalysisAction()
        {
            //使用->分割,得到解析对象。
        }
    }
支付宝
 1     public class StrategyContext2
 2     {
 3         private readonly IList<IStrategy> strategyList = new List<IStrategy>();
 4         /// <summary>
 5         /// 将所有策略都方法
 6         /// </summary>
 7         /// <param name="text"></param>
 8         public StrategyContext2(string text)
 9         {
10             strategyList.Add(new StrategyWeChat(text));
11             strategyList.Add(new StrategyAlipay(text));
12         }
13         /// <summary>
14         /// 调用具体的策略类实现扫码解析方法
15         /// </summary>
16         public void AnalysisAction()
17         {
18             foreach (var item in strategyList)
19             {
20                 if (item.Analysisable)//判断当前策略类是否可以处理
21                     item.AnalysisAction();
22             }
23         }
24     }
Context
1         private StrategyContext2 Context;
2         public void AnalysisAction(string text)
3         {
4             Context = new StrategyContext2(text);
5             Context.AnalysisAction();//自动实现解析,不用关心使用哪种策略
6         }

这样我们就想决定权放到了具体策略类本身中。消灭了If else。如果再添加联通扫码策略的时候,只需要添加联通的具体扫描策略,然后在context构造函数中把他加入到策略集合中。

但是这样我们还是修改了context代码。如果继续想不修改context代码呢?

继续升级

我们可以使用反射,将所有策略实现类都反射出来,添加到策略集合中。那么我们的context类可以这样写:

    public class StrategyContext3
    {
        private readonly IList<IStrategy> strategyList = new List<IStrategy>();
        /// <summary>
        /// 将所有策略都方法
        /// </summary>
        /// <param name="text"></param>
        public StrategyContext3(string text)
        {
            //查询程序集
            Assembly assembly = Assembly.GetExecutingAssembly();
            //找出继承扫描策略接口的类
            IEnumerable<Type> types = assembly.GetTypes().Where(c => c.GetInterface("IStrategy") != null);
            foreach (var t in types)
            {
                object[] parameters = new object[1];
                parameters[0] = text;
                //创建类的实例
                strategyList.Add((IStrategy)Activator.CreateInstance(t, parameters));
            }
        }
        /// <summary>
        /// 调用具体的策略类实现扫码解析方法
        /// </summary>
        public void AnalysisAction()
        {
            foreach (var item in strategyList)
            {
                if (item.Analysisable)//判断当前策略类是否可以处理
                    item.AnalysisAction();
            }
        }
    }
context

应用场景

1、 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。
2、 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
3、 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。

总结

  优点:策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。

  缺点:客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。 这也就是我们所说的if else并没有真正的被消灭。

  改进:我们可以将决定使用哪种策略的权利放到策略类本身中,让策略自己决定到底是不是用我自己的方法。从而实现消灭if else。

     可以通过反射,反射出所有的策略。这样比较符合开闭原则。

 

posted on 2017-08-01 11:55  已经超神  阅读(2377)  评论(8编辑  收藏  举报